﻿// <copyright file="Settings.cs" company="Sinclair Community College">
// Copyright (c) Sinclair Community College. All rights reserved.
// </copyright>

namespace SinclairCC.AppVSuite.Shared
{
    using System.Security.Cryptography;
    using Microsoft.Win32;
    
    /// <summary>
    /// This class manages application settings.
    /// </summary>
    public static class Settings
    {
        /// <summary>
        /// The top-level registry key in which the settings will be stored.
        /// </summary>
        private static RegistryKey rootRegistryKey = Registry.CurrentUser;

        /// <summary>
        /// An additional byte array used to encrypt the data.
        /// </summary>
        private static byte[] optionalEntropy = new byte[] { 8, 6, 7, 5, 3, 0, 9 };

        /// <summary>
        /// Gets a database connection string based on the values stored in the other settings.
        /// </summary>
        public static string ConnectionString
        {
            get
            {
                try
                {
                    System.Data.SqlClient.SqlConnectionStringBuilder stringBuilder = new System.Data.SqlClient.SqlConnectionStringBuilder();
                    stringBuilder.DataSource = GetEncryptedString("Server");
                    stringBuilder.InitialCatalog = GetEncryptedString("Database");
                    stringBuilder.IntegratedSecurity = Settings.UseWindowsAuthentication.HasValue ? Settings.UseWindowsAuthentication.Value : true;
                    if (!stringBuilder.IntegratedSecurity)
                    {
                        stringBuilder.UserID = Settings.SqlUserName;
                        stringBuilder.Password = Settings.SqlPassword;
                    }

                    stringBuilder.PersistSecurityInfo = false;
                    return stringBuilder.ConnectionString;
                }
                catch (System.ArgumentNullException)
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets or sets the name or network address of the instance of SQL server to connect to.
        /// </summary>
        public static string ServerName
        {
            get { return GetEncryptedString("Server"); }
            set { SetEncryptedString("Server", value); }
        }

        /// <summary>
        /// Gets or sets the name of the database associated with the connection.
        /// </summary>
        public static string Database
        {
            get { return GetEncryptedString("Database"); }
            set { SetEncryptedString("Database", value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the current Windows user credentials are used for authentication.
        /// </summary>
        public static bool? UseWindowsAuthentication
        {
            get { return GetBoolean("Windows Authentication"); }
            set { SetBoolean("Windows Authentication", value); }
        }

        /// <summary>
        /// Gets or sets the user ID to be used when connecting to the SQL server.
        /// </summary>
        public static string SqlUserName
        {
            get { return GetEncryptedString("User Name"); }
            set { SetEncryptedString("User Name", value); }
        }

        /// <summary>
        /// Gets or sets the password associated with the SQL user account.
        /// </summary>
        public static string SqlPassword
        {
            get { return GetEncryptedString("Password"); }
            set { SetEncryptedString("Password", value); }
        }

        /// <summary>
        /// Gets the path of the registry key in which all of the settings are stored.
        /// </summary>
        private static string RegistryKeyPath
        {
            get { return string.Format(System.Globalization.CultureInfo.InvariantCulture, @"Software\{0}\{1}", CompanyName, ProductName); }
        }

        /// <summary>
        /// Gets the company name from this class's assembly.
        /// </summary>
        /// <remarks>
        /// If a company name is not specified in the assembly, "My Company" is returned.
        /// </remarks>
        private static string CompanyName
        {
            get
            {
                string returnValue = "My Company";
                System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
                object[] attributes = assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyCompanyAttribute), false);
                foreach (object o in attributes)
                {
                    if (o != null)
                    {
                        returnValue = ((System.Reflection.AssemblyCompanyAttribute)o).Company;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the product name from this class's assembly.
        /// </summary>
        /// <remarks>
        /// If a product name is not specified in the assembly, "My Product" is returned.
        /// </remarks>
        private static string ProductName
        {
            get
            {
                string returnValue = "My Product";
                System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
                object[] attributes = assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyProductAttribute), false);
                foreach (object o in attributes)
                {
                    if (o != null)
                    {
                        returnValue = ((System.Reflection.AssemblyProductAttribute)o).Product;
                    }
                }

                return returnValue;
            }
        }

        /// <summary>
        /// Gets the value of an encrypted string stored in the registry.
        /// </summary>
        /// <param name="valueName">
        /// The name of the value to be retrieved from the registry.
        /// </param>
        /// <returns>
        /// Returns the decrypted value of the string from the registry.
        /// If there is a problem retrieving the specified value from the registry, null is returned.
        /// </returns>
        private static string GetEncryptedString(string valueName)
        {
            string returnValue = null;

            RegistryKey settingsKey = rootRegistryKey.OpenSubKey(RegistryKeyPath, false);
            if (settingsKey != null)
            {
                byte[] encryptedBytes = null;
                object regValue = settingsKey.GetValue(valueName);
                if (regValue != null)
                {
                    encryptedBytes = (byte[])regValue;
                }

                settingsKey.Close();

                if (encryptedBytes != null)
                {
                    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                    byte[] stringBytes = System.Security.Cryptography.ProtectedData.Unprotect(encryptedBytes, optionalEntropy, DataProtectionScope.CurrentUser);
                    returnValue = encoding.GetString(stringBytes);
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Encrypts a string and stores it in the registry.
        /// </summary>
        /// <param name="valueName">
        /// The name of the registry value in which the encrypted string will be stored.
        /// </param>
        /// <param name="value">
        /// The decrypted string to be encrypted and stored in the registry.
        /// </param>
        private static void SetEncryptedString(string valueName, string value)
        {
            RegistryKey settingsKey = rootRegistryKey.CreateSubKey(RegistryKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree);
            if (settingsKey != null)
            {
                System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                byte[] stringBytes = encoding.GetBytes(value);
                byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(stringBytes, optionalEntropy, DataProtectionScope.CurrentUser);
                settingsKey.SetValue(valueName, encryptedData, RegistryValueKind.Binary);
                settingsKey.Flush();
                settingsKey.Close();
            }
        }

        /// <summary>
        /// Gets a Boolean value from the registry.
        /// </summary>
        /// <param name="valueName">
        /// The name of the registry value to be retrieved.
        /// </param>
        /// <returns>
        /// Returns a boolean value based on the specified registry value.
        /// </returns>
        /// <remarks>
        /// This function assumes the specified registry value is stored as a DWORD value.
        /// The returned Boolean value is based on conversion by System.Convert.ToBoolean().
        /// </remarks>
        private static bool? GetBoolean(string valueName)
        {
            bool? returnValue = null;

            RegistryKey settingsKey = rootRegistryKey.OpenSubKey(RegistryKeyPath, false);
            if (settingsKey != null)
            {
                object regValue = settingsKey.GetValue(valueName);
                if (regValue != null)
                {
                    returnValue = System.Convert.ToBoolean((int)regValue);
                }

                settingsKey.Close();
            }

            return returnValue;
        }

        /// <summary>
        /// Stores a Boolean value in the registry.
        /// </summary>
        /// <param name="valueName">
        /// The name of the registry value in which the value will be stored.
        /// </param>
        /// <param name="value">
        /// The Boolean value to be stored in the registry.
        /// </param>
        /// <remarks>
        /// This function stores the specified Boolean value as a DWORD value.
        /// The Boolean value is converted by System.Convert.ToInt32().
        /// </remarks>
        private static void SetBoolean(string valueName, bool? value)
        {
            RegistryKey settingsKey = rootRegistryKey.CreateSubKey(RegistryKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree);
            if (settingsKey != null)
            {
                if (value.HasValue)
                {
                    settingsKey.SetValue(valueName, System.Convert.ToInt32(value.Value), RegistryValueKind.DWord);
                }
                else
                {
                    settingsKey.DeleteValue(valueName);
                }

                settingsKey.Flush();
                settingsKey.Close();
            }
        }
    }
}
